package com.gmrz.uaf.crypto;

import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.crypto.sm.SM2Utils;
import com.gmrz.uaf.protocol.v1.processor.exception.CertificateValidationException;
import com.gmrz.uaf.protocol.v1.processor.exception.GetCertificateException;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.server.ServerConfig;
import com.gmrz.util.Convert;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.X509CertificateStructure;

import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
 * Created by yihui on 2017/6/13.
 */
public class CertUtil {

    private static final Logger LOG = LogManager.getLogger(CertUtil.class);

    private ServerConfig serverConfig = null;

    public CertUtil() {
        this.serverConfig  = GuiceUtil.getProcessorInjector().getInstance(ServerConfig.class);
    }

//    /**
//     * 获取本地根证书公钥
//     * @return
//     * @throws CertificateValidationException
//     */
//    public static byte[] getRootCertPublicKey() throws GetCertificateException {
//        byte[] rootPublicKeyB = null;
//        try{
//            byte[] rootCertByte=new CertUtil().getRootCertFile();
//            LOG.debug("RootCertificate："+ Convert.toHex(rootCertByte));
//            //根证书公钥
//            rootPublicKeyB=getPublicKey(rootCertByte);
//        }catch (Exception ioe){
//            LOG.error("Failed to obtain the Root certificate publicKey");
//        }finally {
//            LOG.debug("RootCertificate PublicKey : " + Convert.toHex(rootPublicKeyB));
//            return rootPublicKeyB;
//        }
//    }

    //获取公钥
    public static byte[] getPublicKey(byte[] certByte){
        InputStream inStream = new ByteArrayInputStream(certByte);
        ASN1Sequence seq = null;
        ASN1InputStream aIn;
        byte[] pk = null;
        try{
            aIn = new ASN1InputStream(inStream);
            seq = (ASN1Sequence)aIn.readObject();
            X509CertificateStructure cert = new X509CertificateStructure(seq);
            SubjectPublicKeyInfo subjectPublicKeyInfo = cert.getSubjectPublicKeyInfo();
            DERBitString publicKeyData = subjectPublicKeyInfo.getPublicKeyData();
            byte[] publicKey = publicKeyData.getEncoded();
            byte[] encodedPublicKey = publicKey;
            pk = new byte[64];
            System.arraycopy(encodedPublicKey, 4, pk, 0, pk.length);

        }catch (IOException ioe){
            LOG.error("Failed to obtain the Root certificate.");
            throw new GetCertificateException(UAFErrorCode.GET_CACERTIFICATE_PUBLICKEY_FAILED, ioe);
        }finally {
            LOG.debug("Certificate PublicKey:" + Convert.toHex(pk));
            return pk;
        }
    }
    //获取签名数据
    public static byte[] getSignature(byte[] certByte) throws GetCertificateException{
        InputStream inStream = new ByteArrayInputStream(certByte);
        ASN1Sequence seq = null;
        ASN1InputStream aIn;
        byte[] signature= null;
        try{
            aIn = new ASN1InputStream(inStream);
            seq = (ASN1Sequence)aIn.readObject();
            X509CertificateStructure cert = new X509CertificateStructure(seq);
            DERBitString derSignature=cert.getSignature();
            byte[] signatureByte=derSignature.getEncoded();
            signature = new byte[signatureByte.length-3];
            System.arraycopy(signatureByte, 3, signature, 0, signature.length);
        }catch (IOException ioe){
            LOG.error("Failed to obtain the Signature.");
            throw new GetCertificateException(UAFErrorCode.GET_CACERTIFICATE_SIGNATURE_FAILED, ioe);
        }finally {
            LOG.debug("Certificate Signature:" + Convert.toHex(signature));
            return signature;
        }
    }
    //获取待签名的原报文
    public static byte[] getSignatureData(byte[] certByte) throws GetCertificateException{
        InputStream inStream = new ByteArrayInputStream(certByte);
        ASN1Sequence seq = null;
        ASN1InputStream aIn;
        byte[] signatureData = null;
        try{
            aIn = new ASN1InputStream(inStream);
            seq = (ASN1Sequence)aIn.readObject();
            X509CertificateStructure cert = new X509CertificateStructure(seq);
            signatureData=cert.getTBSCertificate().getEncoded();
        }catch (IOException ioe){
            LOG.error("Failed to obtain the SignatureData.");
            throw new GetCertificateException(UAFErrorCode.GET_CACERTIFICATE_SIGNATUREDATA_FAILED, ioe);
        }finally {
            LOG.debug("Certificate SignatureData:" + Convert.toHex(signatureData));
            return signatureData;
        }
    }

    //验证自签名的根证书
    public static boolean validateRootCertificate(byte[] certByte)throws CertificateValidationException {
        LOG.debug("ValidateRootCertificate");
        boolean isValid=false;
        try {
            // 从证书中获取公钥
            byte[] publicKeyB= getPublicKey(certByte);
            // 从证书中获取待签名的原数据
            byte[] signedDataB=getSignatureData(certByte);
            // 从证书中获取签名
            byte[] signatureB=getSignature(certByte);
            if( publicKeyB==null || signedDataB==null || signatureB==null){
                throw new CertificateValidationException(UAFErrorCode.VALIDATE_CACERTIFICATE_FAILED);
            }
            isValid = SM2Utils.verifySign(publicKeyB, signedDataB, signatureB);
        } catch (Exception cpve) {
            LOG.error("Failed to validate certificate - ", cpve);
            throw new CertificateValidationException(UAFErrorCode.VALIDATE_CACERTIFICATE_FAILED, cpve);
        }finally {
            LOG.debug("ValidateRootCertificate " + (isValid ? "success" : "failed"));
            return isValid;
        }
    }

    //验证二级证书
    public static boolean validateSecondLevelCertificate(byte[] secondLevelCertByte,byte[] rootCertByte)throws CertificateValidationException {
        LOG.debug("validateSecondLevelCertCertificate");
        boolean result=false;
        try {
            //byte[] rootCertByte=new CertUtil().getRootCertFile();
            LOG.debug("RootCertificate："+Convert.toHex(rootCertByte));
            // 从根证书中获取CA的公钥
            byte[] rootPublicKeyB=getPublicKey(rootCertByte);
            LOG.debug("RootCertificate PublicKey : " + Convert.toHex(rootPublicKeyB));

            LOG.debug("SecondLevelCertificate : " + Convert.toHex(secondLevelCertByte));

            // 从二级证书（设备证书）中获取公钥
            byte[] secondLevelCertPublicKeyB=getPublicKey(secondLevelCertByte);
            LOG.debug("SecondLevelCertificate PublicKey : " + Convert.toHex(secondLevelCertPublicKeyB));

            // 从二级证书（设备证书）中获取待签名数据（包括：证书序列哈、颁发机构、有效期、持有人、公钥等数据）
            byte[] signedDataB=getSignatureData(secondLevelCertByte);
            LOG.debug("SecondLevelCertificate SignatureData : " + Convert.toHex(signedDataB));
            // 从二级证书（设备证书）中获取签名
            byte[] signatureB=getSignature(secondLevelCertByte);
            LOG.debug("SecondLevelCertificate Signature : " + Convert.toHex(signatureB));
            // 设备证书校验，使用根证书解密签名，获取摘要及hash算法，然后使用hash算法对待签名数据进行hash得到摘要，将两个摘要进行对比
            result = SM2Utils.verifySecondLevelCertSign(secondLevelCertPublicKeyB,rootPublicKeyB, signedDataB, signatureB);

        } catch (Exception cpve) {
            LOG.error("Failed to validate certificate - ", cpve);
            throw new CertificateValidationException(UAFErrorCode.VALIDATE_CACERTIFICATE_FAILED, cpve);
        }finally {
            LOG.debug("ValidateSecondLevelCertificate " + (result ? "success" : "failed"));
            return result;
        }
    }



//    public  byte[] getRootCertFile() throws GetCertificateException {
//        try{
//            String cert=serverConfig.getRootCert();
//            byte[] certByte = Convert.toByteArray(cert);
//            if ((certByte == null) || certByte.length==0) {
//                throw new GetCertificateException(UAFErrorCode.GET_ROOT_CACERTIFICATE_FAILED, "Failed to obtain the certificate.");
//            }
//            LOG.debug("Root Certificate："+Convert.toHex(certByte));
//            return certByte;
//        }catch (Exception e){
//            LOG.error("Failed to obtain the Root certificate- ", e);
//            throw new GetCertificateException(UAFErrorCode.GET_ROOT_CACERTIFICATE_FAILED, e);
//        }
//    }


    /**
     * SM2验证签名
     * @param publicKey     公钥信息
     * @param signedData    待签名数据
     * @param signature     签名值
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws ClassNotFoundException
     */
    public static boolean verifySignature(byte[] publicKey, byte[] signedData, byte[] signature) throws CertificateValidationException {
        LOG.info("Into SM2 method verifySignature by publicKey");
        boolean isValid =false;
        try {
            isValid = SM2Utils.verifySign(publicKey, signedData, signature);
        } catch (Exception e) {
            LOG.error("Failed to validate certificate - ", e);
            throw new CertificateValidationException(UAFErrorCode.VALIDATE_CACERTIFICATE_FAILED, e);
        }
        LOG.debug("Verify Signature " + (isValid ? "success" : "failed"));
        return isValid;
    }


    /**
     * SM2验证签名
     * @param cert          公钥证书
     * @param signedData
     * @param signature
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws ClassNotFoundException
     */
    public static boolean verifySignatureByCert(byte[] cert, byte[] signedData, byte[] signature) throws CertificateValidationException {
        LOG.debug("Into SM2 method verifySignature by certificate");
        boolean isValid =false;
        try {
            byte[] publicKey=getPublicKey(cert);
            byte[] signR=null;
            int stand =  Convert.byteToInt((byte)0x80);
            if(Convert.byteToInt(signature[0])>stand){
                signR=Convert.toByteArray("022100"+Convert.toHex(signature).substring(0,64));
            }else{
                signR=Convert.toByteArray("0220"+Convert.toHex(signature).substring(0,64));
            }
            byte[] signS=null;
            if(Convert.byteToInt(signature[32])>stand){
                signS=Convert.toByteArray("022100"+Convert.toHex(signature).substring(64,Convert.toHex(signature).length()));
            }else{
                signS=Convert.toByteArray("0220"+Convert.toHex(signature).substring(64,Convert.toHex(signature).length()));
            }
            byte[] tag=new byte[1];
            tag[0]=(byte)0x30;
            byte[] length=new byte[1] ;
            length[0]=(byte) (signR.length+signS.length);
            byte[] sig=new byte[2+signR.length+signS.length];
            System.arraycopy(tag,0,sig,0,1);
            System.arraycopy(length,0,sig,1,1);
            System.arraycopy(signR,0,sig,2,signR.length);
            System.arraycopy(signS,0,sig,2+signR.length,signS.length);
            LOG.debug("verifySignatureByCert publicKey:"+Convert.toHex(publicKey));
            LOG.debug("verifySignatureByCert signedData:"+Convert.toHex(signedData));
            LOG.debug("verifySignatureByCert sig:"+Convert.toHex(sig));
            isValid = SM2Utils.verifySign(publicKey, signedData, sig);
        } catch (Exception e) {
            LOG.error("Failed to validate certificate - ", e);
            throw new CertificateValidationException(UAFErrorCode.VALIDATE_CACERTIFICATE_FAILED, e);
        }
        LOG.debug("Verify Signature " + (isValid ? "success" : "failed"));
        return isValid;
    }



    public static void main(String[] args) throws CertificateValidationException {
//        byte[] s=Convert.toByteArray("45C9FB24A4FB98DF73BC60824A6A5978D43EB2D51F8B49AA3B1057CDDE13E6740497305DD14045E5FD29D7E2E74B170F395CE2D010663696A4A0A826FE0472F6");
//        byte[] s1=Convert.toByteArray("07557E0012520000035220009D23B285D5E9DF7158E8F230F3E79D5607FF48CBE4C3EE3B26F6BF333AAF96C60555040000000100065520000DACDA69E9799CFBA27367A8CBFCC57A48620E5949259D573046E901B4A247F205520200010015522000CEC12F7DA2D0F8E61ED651AE3DDF3BA89AE0A848F69AA8B375B45CD202F750CE");
//        byte[] s2=Convert.toByteArray("3045022100AC7FF305FAC227FF42A9E2E3126DCA3BBDD35CFD2F98405D1A72699DF7A049C8022049F1BA094DDFCE9DEF148DF0687774AE482D5CEE39ECA1616CA72DB946A6CBAE");
//        try {
//            verifySignature(s,s1,s2);
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//        Gson gson = UAFSchemaBuilder.getGson();
//        List<String> a=new ArrayList<String>();
//        a.add("PIN");
//        a.add("FINGER");

        String rootCert = "MIIBvTCCAVqgAwIBAgIBATAUBggqgRzPVQGDdQYIKoEcz1UBg3UwQTELMAkGA1UEBhMCQ04xDzANBgNVBAoTBkxFTk9WTzEPMA0GA1UEAxMGUk9PVENBMRAwDgYDVQQHEwdCRUlKSU5HMB4XDTE3MDYwODEzMDYwOVoXDTIyMDYwNzEzMDYwOVowQTELMAkGA1UEBhMCQ04xDzANBgNVBAoTBkxFTk9WTzEPMA0GA1UEAxMGUk9PVENBMRAwDgYDVQQHEwdCRUlKSU5HMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE1mQ3OEyzflkQ4PJhlSQ2UgSxuv17ZAjs0HfyuyZmq9BA/4eB1t0m39BcBzjPGBjglA3/YnibFd460yvmWQ8ejqM5MDcwCQYDVR0TBAIwADALBgNVHQ8EBAMCA+gwHQYDVR0OBBYEFNo5o+5ea0sNMlW/75VgGJCv2AcJMBQGCCqBHM9VAYN1BggqgRzPVQGDdQNHADBEAiB0n6cyreDBrVVyZL1h1vzlTO34AeSHZpUZmyJwDsyk3QIgX2KAE14jXOF39wtOazPufiJ2gTURtEQa32w+VdSn4z8\\u003d";

        byte[] rootBytes = Convert.fromBase64(rootCert);
        boolean f = validateRootCertificate(rootBytes);
    }

}
